// 全局定义一个待收集的函数
let activeReactiveFn = null

/**
 * Depend优化:
 *  1> depend方法
 *  2> 使用Set来保存依赖函数, 而不是数组[]
 */

// 创建 Depend 类，用来收集函数依赖和运行函数依赖的函数
class Depend {
  constructor() {
    this.reactiveFns = new Set()
  }
  // 添加函数进依赖
  depend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn)
    }
  }
  // 遍历运行依赖
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn()
    })
  }
}

/**
 * 封装一个响应式的函数
 * @param {Function} fn // 需要添加到某一依赖的函数
 * @return {undefined}
*/
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 定义一个 targetMap 里面映射所有用 reactive 函数生成的对象
const targetMap = new WeakMap()

/**
 * 封装一个根据对象和属性获取对应 depend 的函数
 * @param {Object} target // 目标对象
 * @param {String | Symbol} key // 目标属性
 * @return {Depend}
*/
function getDepend(target, key) {
  // 根据对象找到它的映射表 objMap
  let objMap = targetMap.get(target)
  if (!objMap) {
    objMap = new Map()
    targetMap.set(target, objMap)
  }

  // 根据属性找到其依赖
  let keyDepend = objMap.get(key)
  if (!keyDepend) {
    keyDepend = new Depend()
    objMap.set(key, keyDepend)
  }
  return keyDepend
}

/**
 * 封装一个函数, 返回响应式对象
 * @param {Object} obj // 需要变成响应式的对象
 * @return {Proxy}
*/
function reactive(obj) {
  return new Proxy(obj, {
    // 当调用 get 函数时，将该函数放进依赖里面
    get: function(target, key, receiver) {
      const objDepend = getDepend(target, key) // 获取该属性的 Depend 类
      objDepend.depend()
      return Reflect.get(target, key, receiver)
    },

    // 当调用 set 函数时，将该属性的依赖调用一遍
    set: function(target, key, newVal, receiver) {
      Reflect.set(target, key, newVal, receiver)
      const objDepend = getDepend(target, key) // 获取该属性的 Depend 类
      objDepend.notify()
    }
  })
}

// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
  name: "why", // depend对象
  age: 18 // depend对象
})

const infoProxy = reactive({
  address: "广州市",
  height: 1.88
})

watchFn(() => {
  console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})

foo.name = "bar"
